package com.cch.cooperation.api.bookkeepping.service;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cch.cooperation.api.bookkeepping.dto.*;
import com.cch.cooperation.api.common.sdk.openai.OpenAiClient;
import com.cch.cooperation.api.support.dto.PushAppNoticeDTO;
import com.cch.cooperation.api.support.service.PushServiceImpl;
import com.cch.cooperation.api.zone.service.ZoneServiceImpl;
import com.cch.cooperation.api.zone.service.ZoneUserServiceImpl;
import com.cch.cooperation.biz.common.page.PageReq;
import com.cch.cooperation.biz.common.page.QueryPageProxy;
import com.cch.cooperation.biz.common.util.BeanUtilEx;
import com.cch.cooperation.common.exception.BizException;
import com.cch.cooperation.common.page.PageList;
import com.cch.cooperation.common.util.SnowflakeIdUtil;
import com.cch.cooperation.dao.bookkeeping.dto.BookkeepingQueryDTO;
import com.cch.cooperation.dao.bookkeeping.enums.BookkeepingCategoryEnum;
import com.cch.cooperation.dao.bookkeeping.enums.BookkeepingEnum;
import com.cch.cooperation.dao.bookkeeping.mapper.BookkeepingMapper;
import com.cch.cooperation.dao.bookkeeping.model.Bookkeeping;
import com.cch.cooperation.dao.bookkeeping.model.BookkeepingCategory;
import com.cch.cooperation.dao.zone.dto.ZoneUserDTO;
import com.cch.cooperation.dao.zone.model.Zone;
import com.google.common.collect.Lists;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 记账服务实现类
 *
 * @author cch
 */
@Service
public class BookkeepingServiceImpl {

    @Resource
    private BookkeepingMapper mapper;
    @Resource
    private OpenAiClient openAiClient;
    @Resource
    private BookkeepingCategoryServiceImpl bookkeepingCategoryService;
    @Resource
    private ZoneUserServiceImpl zoneUserService;
    @Resource
    private ZoneServiceImpl zoneService;
    @Resource
    private PushServiceImpl pushService;

    public BookkeepingPageListDTO queryList(BookkeepingQueryDTO query, PageReq pageReq) {
        // 分页查询日期
        PageList<String> datePage = QueryPageProxy.query(() -> mapper.queryDistinctDate(query), pageReq.getPage(), pageReq.getPageSize());
        if (CollUtil.isEmpty(datePage.getRows())) {
            return new BookkeepingPageListDTO(0L, new ArrayList<>());
        }
        // 根据分页日期查询流水
        LambdaQueryWrapper<Bookkeeping> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StrUtil.isNotEmpty(query.getUserId()), Bookkeeping::getUserId, query.getUserId());
        queryWrapper.eq(StrUtil.isNotEmpty(query.getCategoryId()), Bookkeeping::getCategoryId, query.getCategoryId());
        queryWrapper.eq(StrUtil.isNotEmpty(query.getZoneId()), Bookkeeping::getZoneId, query.getZoneId());
        queryWrapper.eq(Objects.nonNull(query.getType()), Bookkeeping::getType, query.getType());
        queryWrapper.ge(Bookkeeping::getDate, DateUtil.parse(datePage.getRows().get(datePage.getRows().size() - 1)));
        queryWrapper.le(Bookkeeping::getDate, DateUtil.endOfDay(DateUtil.parse(datePage.getRows().get(0))));
        queryWrapper.orderByDesc(Bookkeeping::getDate, Bookkeeping::getId);
        List<Bookkeeping> list = mapper.selectList(queryWrapper);
        // 转为 BookkeepingDTO
        List<BookkeepingDTO> bookkeeping = convertToDTO(list.stream().map(o -> o.getId()).collect(Collectors.toList()));
        // 分组后返回
        Map<String, List<BookkeepingDTO>> bookkeepingGroup = bookkeeping.stream().collect(Collectors.groupingBy(o -> DateUtil.format(o.getDate(), "yyyy-MM-dd")));
        List<BookkeepingGroupDTO> rows = bookkeepingGroup.entrySet().stream().map(o -> new BookkeepingGroupDTO(DateUtil.parse(o.getKey()), o.getValue())).sorted(Comparator.comparing(BookkeepingGroupDTO::getDate).reversed()).collect(Collectors.toList());
        BookkeepingPageListDTO result = new BookkeepingPageListDTO(datePage.getTotal(), rows);
        // 计算总收入和总支出
        BookkeepingQueryDTO sumQuery = BeanUtil.copyProperties(query, BookkeepingQueryDTO.class);
        sumQuery.setType(BookkeepingEnum.Type.INCOME.value);
        result.setTotalIncome(Optional.ofNullable(mapper.sumAmount(sumQuery)).orElse(BigDecimal.ZERO));
        sumQuery.setType(BookkeepingEnum.Type.EXPENSE.value);
        result.setTotalExpense(Optional.ofNullable(mapper.sumAmount(sumQuery)).orElse(BigDecimal.ZERO));
        return result;
    }

    public List<BookkeepingDTO> addByDescription(BookkeepingDescriptionAddDTO req) {
        if (StrUtil.isEmpty(req.getDescription())) {
            throw new BizException("描述不能为空");
        }
        if (StrUtil.isEmpty(req.getZoneId())) {
            throw new BizException("空间id不能为空");
        }
        if (StrUtil.isEmpty(req.getUserId())) {
            throw new BizException("用户id不能为空");
        }
        List<Bookkeeping> adds = inferByDescription(req.getDescription());
        if (CollUtil.isEmpty(adds)) {
            throw new BizException("未识别到消费信息哦～");
        }
        Date now = new Date();
        for (Bookkeeping add : adds) {
            if (Objects.isNull(add.getDate())) {
                add.setDate(now);
            }
            add.setId(SnowflakeIdUtil.getIdStr());
            add.setZoneId(req.getZoneId());
            add.setCreateTime(now);
            add.setUpdateTime(now);
            add.setUserId(req.getUserId());
            add.setRemark(req.getDescription());
            mapper.insert(add);
        }
        // 转为 dto 后返回
        return convertToDTO(adds.stream().map(o -> o.getId()).collect(Collectors.toList()));
    }

    public BookkeepingDTO convertToDTO(String id) {
        List<BookkeepingDTO> result = convertToDTO(Lists.newArrayList(id));
        return CollUtil.isEmpty(result) ? null : result.get(0);
    }

    public List<BookkeepingDTO> convertToDTO(List<String> ids) {
        List<BookkeepingDTO> result = BeanUtilEx.copy(mapper.selectBatchIds(ids), BookkeepingDTO.class);
        // 设置类别
        List<String> categoryIds = result.stream().map(o -> o.getCategoryId()).distinct().collect(Collectors.toList());
        Map<String, BookkeepingCategory> categoryMap = bookkeepingCategoryService.getMapByIds(categoryIds);
        result.forEach(o -> o.setCategory(categoryMap.get(o.getCategoryId())));
        // 设置用户信息
        Map<String, List<BookkeepingDTO>> bookkeepingGroup = result.stream().collect(Collectors.groupingBy(Bookkeeping::getZoneId));
        for (Map.Entry<String, List<BookkeepingDTO>> entry : bookkeepingGroup.entrySet()) {
            List<BookkeepingDTO> zoneBookkeeping = entry.getValue();
            List<String> userIds = zoneBookkeeping.stream().map(Bookkeeping::getUserId).distinct().collect(Collectors.toList());
            Map<String, ZoneUserDTO> userMap = zoneUserService.getMapByUserIds(entry.getKey(), userIds);
            zoneBookkeeping.forEach(o -> o.setUser(userMap.get(o.getUserId())));
        }
        // 设置空间信息
        List<String> zoneIds = result.stream().map(o -> o.getZoneId()).distinct().collect(Collectors.toList());
        Map<String, Zone> zoneMap = zoneService.getMapByIds(zoneIds);
        result.forEach(o -> o.setZone(zoneMap.get(o.getZoneId())));
        // 根据ids 排序
        result.sort(Comparator.comparing(o -> ids.indexOf(o.getId())));
        return result;
    }

    public BookkeepingDTO add(BookkeepingAddDTO req) {
        Date now = new Date();
        req.setId(SnowflakeIdUtil.getIdStr());
        req.setCreateTime(now);
        req.setUpdateTime(now);
        mapper.insert(req);
        // 推送
        if (Boolean.TRUE.equals(req.getIsPushAppNotice())) {
            PushAppNoticeDTO pushReq = new PushAppNoticeDTO();
            pushReq.setId(SnowflakeIdUtil.getIdStr());
            pushReq.setTitle("记录成功");
            pushReq.setBody(req.getRemark() + "，金额：" + req.getAmount());
            pushReq.setUserId(req.getUserId());
            pushService.pushAppNotice(pushReq);
        }
        return convertToDTO(req.getId());
    }

    public BookkeepingDTO updateById(Bookkeeping req) {
        if (StrUtil.isEmpty(req.getId())) {
            throw new BizException("id不能为空");
        }
        req.setUpdateTime(new Date());
        mapper.updateById(req);
        return convertToDTO(req.getId());
    }

    public List<Bookkeeping> inferByDescription(String description) {
        if (StrUtil.isEmpty(description)) {
            return new ArrayList<>();
        }
        List<BookkeepingCategory> categories = bookkeepingCategoryService.queryList(BookkeepingCategoryEnum.Type.DEFAULT.value);
        String expenseCategories = categories.stream().filter(o -> BookkeepingCategoryEnum.BillType.EXPENSE.value.equals(o.getBillType())).map(o -> o.getName() + "=" + o.getId()).collect(Collectors.joining(";"));
        String incomeCategories = categories.stream().filter(o -> BookkeepingCategoryEnum.BillType.INCOME.value.equals(o.getBillType())).map(o -> o.getName() + "=" + o.getId()).collect(Collectors.joining(";"));
        String prompt = "请根据以下描述和分类将描述根据支出和收入分类。不存在流水号时不同类型返回多条，相同的类型将金额求和并合并为一条数据；存在流水号时按流水号分组后分类；无法归类时选择最接近的类别；无任何有效流水信息返回空列表。结果仅需返回 json 列表\n"
                + "返回数据字段：categoryId=类别id, amount=金额, type=流水类型枚举（1.支出 2.收入）, billNo=流水号（描述中没有则为null）,date=日期(描述中没有日期则返回当前时间，有相对日期的用当前时间换算，时间格式：yyyy-MM-dd HH:mm:ss)\n"
                + "当前时间：" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss") + "\n"
                + "支出类别：" + expenseCategories + "\n"
                + "收入类别：" + incomeCategories + "\n"
                + "附加说明：数码仅用于数码机器产品消费归类；食材消费也归属餐饮\n"
                + "描述：" + description + "\n";
        String json = openAiClient.infer("qwen-flash", prompt);
        json = json.startsWith("```json") ? json.substring(8, json.length() - 3) : json;
        return JSON.parseArray(json, Bookkeeping.class);
    }

    public void deleteById(String id) {
        if (StrUtil.isEmpty(id)) {
            throw new BizException("id不能为空");
        }
        mapper.deleteById(id);
    }

}
